package top.cardone.security.shiro.web.filter.authc.impl;

import com.google.common.collect.Maps;
import com.google.gson.Gson;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.apache.shiro.web.util.SavedRequest;
import top.cardone.context.ApplicationContextHolder;
import top.cardone.context.util.CodeExceptionUtils;
import top.cardone.core.CodeException;
import top.cardone.security.shiro.authc.impl.UsernamePasswordTokenImpl;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;

/**
 * Created by yht on 16-2-2.
 */
@Log4j2
public class FormAuthenticationFilterImpl extends FormAuthenticationFilter {
    @Setter
    private String validationCodeParam = "KAPTCHA_SESSION_KEY";

    @Setter
    private String validationParam = "validation-login";

    /**
     * 单个对象键名
     */
    @Setter
    private String objectKey = "data";

    protected boolean onLoginSuccess(AuthenticationToken token, Subject subject,
                                     ServletRequest request, ServletResponse response) throws Exception {
        if (StringUtils.isNotBlank(validationParam)) {
            subject.getSession().removeAttribute(this.validationParam);
        }

        if (!StringUtils.startsWith(request.getContentType(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
            return super.onLoginSuccess(token, subject, request, response);
        }

        String successUrl = StringUtils.EMPTY;

        SavedRequest savedRequest = org.apache.shiro.web.util.WebUtils.getAndClearSavedRequest(request);

        if (savedRequest != null && savedRequest.getMethod().equalsIgnoreCase(AccessControlFilter.GET_METHOD)) {
            String requestURI = getPathWithinApplication(request);

            if (!StringUtils.equals(savedRequest.getRequestUrl(), requestURI)) {
                successUrl = savedRequest.getRequestUrl();
            }
        } else {
            successUrl = this.getSuccessUrl();
        }

        response.setCharacterEncoding(CharEncoding.UTF_8);
        response.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE);

        try (Writer out = response.getWriter()) {
            Map<String, Object> successMap = Maps.newHashMap();

            successMap.put(objectKey, successUrl);

            String json = ApplicationContextHolder.getBean(Gson.class).toJson(successMap);

            out.write(json);

            out.flush();
        } catch (java.io.IOException ex) {
            log.error(ex.getMessage(), ex);
        }

        return false;
    }

    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e,
                                     ServletRequest request, ServletResponse response) {
        if (StringUtils.isNotBlank(validationParam)) {
            SecurityUtils.getSubject().getSession().setAttribute(validationParam, true);
        }

        if (!StringUtils.startsWith(request.getContentType(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
            return super.onLoginFailure(token, e, request, response);
        }

        response.setCharacterEncoding(CharEncoding.UTF_8);
        response.setContentType(org.springframework.http.MediaType.APPLICATION_JSON_UTF8_VALUE);

        Map<String, String> errorInfo;

        try (Writer out = response.getWriter()) {
            String requestURI = getPathWithinApplication(request);

            if (e instanceof IncorrectCredentialsException) {
                errorInfo = CodeExceptionUtils.newMap(requestURI, "000004", "用户名密码不正确");
            } else {
                errorInfo = CodeExceptionUtils.newMap(requestURI, e);
            }

            String json = ApplicationContextHolder.getBean(Gson.class).toJson(errorInfo);

            out.write(json);
        } catch (java.io.IOException ex) {
            log.error(ex.getMessage(), ex);
        }

        return false;
    }

    private Map<String, Object> getByJson(ServletRequest request) {
        String jsonString;

        try (java.io.InputStream is = request.getInputStream()) {
            jsonString = IOUtils.toString(is, request.getCharacterEncoding());
        } catch (IOException e) {
            throw new CodeException("000012", new String[]{e.getMessage()}, e);
        }

        return ApplicationContextHolder.getBean(Gson.class).fromJson(jsonString, Map.class);
    }

    private Map<String, Object> getByParameter(ServletRequest request) {
        Map<String, Object> parameterMap = org.springframework.web.util.WebUtils.getParametersStartingWith(request, null);

        return parameterMap;
    }

    protected AuthenticationToken createToken(String username, String password,
                                              ServletRequest request, ServletResponse response) {
        Boolean validation = true;

        if (StringUtils.isNotBlank(validationParam)) {
            validation = BooleanUtils.isTrue((Boolean) SecurityUtils.getSubject().getSession().getAttribute(validation));
        }

        UsernamePasswordTokenImpl usernamePasswordToken = new UsernamePasswordTokenImpl();

        Map<String, Object> parameterMap;

        if (StringUtils.startsWith(request.getContentType(), org.springframework.http.MediaType.APPLICATION_JSON_VALUE)) {
            parameterMap = this.getByJson(request);
        } else {
            parameterMap = this.getByParameter(request);
        }

        String validationCode = null;

        String serviceValidationCode = null;

        if (StringUtils.isNotBlank(this.validationCodeParam) && validation) {
            validationCode = MapUtils.getString(parameterMap, validationCodeParam);

            serviceValidationCode = (String) SecurityUtils.getSubject().getSession().getAttribute(this.validationCodeParam);

            SecurityUtils.getSubject().getSession().removeAttribute(this.validationCodeParam);
        }

        username = MapUtils.getString(parameterMap, this.getUsernameParam());

        password = MapUtils.getString(parameterMap, this.getPasswordParam());

        boolean rememberMe = BooleanUtils.toBoolean(MapUtils.getString(parameterMap, this.getRememberMeParam()));

        String host = getHost(request);

        usernamePasswordToken.setUsername(username);
        usernamePasswordToken.setPassword(password != null ? password.toCharArray() : null);
        usernamePasswordToken.setValidation(validation);
        usernamePasswordToken.setValidationCode(validationCode);
        usernamePasswordToken.setServiceValidationCode(serviceValidationCode);
        usernamePasswordToken.setHost(host);
        usernamePasswordToken.setRememberMe(rememberMe);

        return usernamePasswordToken;
    }
}
